
package com.jstarcraft.ai.jsat.distributions.kernels;

import com.jstarcraft.ai.jsat.DataSet;
import com.jstarcraft.ai.jsat.distributions.Distribution;
import com.jstarcraft.ai.jsat.distributions.LogUniform;
import com.jstarcraft.ai.jsat.distributions.Uniform;
import com.jstarcraft.ai.jsat.linear.Vec;

/**
 * Provides an implementation of the Sigmoid (Hyperbolic Tangent) Kernel, which
 * is of the form:<br>
 * k(x, y) = tanh(alpha * &lt; x, y &gt; +c)<br>
 * Technically, this kernel is not positive definite.
 * 
 * @author Edward Raff
 */
public class SigmoidKernel extends BaseKernelTrick {

    private static final long serialVersionUID = 8066799016611439349L;
    private double alpha;
    private double c;

    /**
     * Creates a new Sigmoid Kernel
     * 
     * @param alpha the scaling factor for the dot product
     * @param C     the additive constant
     */
    public SigmoidKernel(double alpha, double C) {
        this.alpha = alpha;
        this.c = C;
    }

    /**
     * Creates a new Sigmoid Kernel with a bias term of 1
     * 
     * @param alpha the scaling factor for the dot product
     */
    public SigmoidKernel(double alpha) {
        this(alpha, 1);
    }

    /**
     * Sets the scaling factor for the dot product, this is equivalent to
     * multiplying each value in the data set by a constant factor
     * 
     * @param alpha the scaling factor
     */
    public void setAlpha(double alpha) {
        if (Double.isInfinite(alpha) || Double.isNaN(alpha) || alpha == 0)
            throw new IllegalArgumentException("alpha must be a real non zero value, not " + alpha);
        this.alpha = alpha;
    }

    /**
     * Returns the scaling parameter
     * 
     * @return the scaling parameter
     */
    public double getAlpha() {
        return alpha;
    }

    /**
     * Sets the additive term, when set to one this is equivalent to adding a bias
     * term of 1 to each vector. This is done after the scaling by
     * {@link #setAlpha(double) alpha}.
     * 
     * @param c the non negative additive term
     */
    public void setC(double c) {
        if (c < 0 || Double.isNaN(c) || Double.isInfinite(c))
            throw new IllegalArgumentException("C must be non negative, not " + c);
        this.c = c;
    }

    /**
     * Returns the additive constant
     * 
     * @return the additive constant
     */
    public double getC() {
        return c;
    }

    @Override
    public double eval(Vec a, Vec b) {
        return Math.tanh(alpha * a.dot(b) + c);
    }

    /**
     * Guesses a distribution for the &alpha; parameter
     *
     * @param d the data to get the guess for
     * @return a distribution for the &alpha; parameter
     */
    public static Distribution guessAlpha(DataSet d) {
        return new LogUniform(1e-12, 1e3);// from A Study on Sigmoid Kernels for SVM and the Training of non-PSD Kernels
                                          // by SMO-type Methods
    }

    /**
     * Guesses a distribution for the &alpha; parameter
     *
     * @param d the data to get the guess for
     * @return a distribution for the &alpha; parameter
     */
    public static Distribution guessC(DataSet d) {
        return new Uniform(-2.4, 2.4);// from A Study on Sigmoid Kernels for SVM and the Training of non-PSD Kernels
                                      // by SMO-type Methods
    }

    @Override
    public SigmoidKernel clone() {
        return new SigmoidKernel(alpha, c);
    }
}
